package org.ucas.faker.web.service.Visualization;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import jdk.nashorn.internal.runtime.JSONFunctions;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.ucas.faker.web.Config.VisualConfig;
import org.ucas.faker.web.service.Visualization.entity.AxisParam;
import org.ucas.faker.web.service.Visualization.entity.SeriesParam;
import org.ucas.faker.web.service.Visualization.entity.Symbol;
import org.ucas.faker.web.service.Visualization.json.DataUtil;
import org.ucas.faker.web.service.Visualization.json.JsonUtil;

import java.awt.*;
import java.util.List;

@Service
@Transactional
public class VisualService {

    protected <T> JSONObject getSeries(SeriesParam seriesParam){
        String graphType = seriesParam.getGraphType();
        String name = seriesParam.getSeriesName();
        List<T> data = seriesParam.getDataList();
        JSONArray label = seriesParam.getLabel();
        switch (graphType){
            case VisualConfig.TYPE_PIE:
                return getSeriesPie(name, data, label);
            case VisualConfig.TYPE_STRIPE:
            case VisualConfig.TYPE_BAR:
                return getSeriesBar(graphType, name, data);
            case VisualConfig.TYPE_LINE:
                return getSeriesLine(name, data, seriesParam.getOption().getSymbol());
            case VisualConfig.TYPE_STACKSTRIPE:
            case VisualConfig.TYPE_STACKBAR:
            case VisualConfig.TYPE_STACKBARRATIO:
            case VisualConfig.TYPE_STACKSTRIPERATIO:
                return getSeriesStackbar(graphType, name, data);
            case VisualConfig.TYPE_STACKLINE:
            case VisualConfig.TYPE_STACKLINERATIO:
                return getSeriesStackline(name, data);
            case VisualConfig.TYPE_BUBBLE:
                return getSeriesBubble(name, data, label, seriesParam.getOption().getMinValue(), seriesParam.getOption().getMaxValue());
            case VisualConfig.TYPE_2TARGET:
                return getSeries2Target(seriesParam.getMultiDimentionData());
            default:
                return new JSONObject();
        }
    }

    private <T> JSONObject getSeries2Target(List<T[]> list){
        JSONObject json = new JSONObject();
        json.put("type", "line");
        json.put("smooth", true);
        json.put("symbolSize", 9);
        JSONArray dataArray = new JSONArray();
        for (T[] value : list){
            dataArray.add(JsonUtil.arrayToJsonArray(value));
        }
        json.put("data", dataArray);
        return json;
    }

    private <T> JSONObject getSeriesBubble(String name, List<T> list, JSONArray basisLabel, double min, double max){
        JSONObject json = new JSONObject();
        json.put("name", name);
        json.put("type", "scatter");
        JSONArray dataArray = new JSONArray();
        for (int i=0;i<list.size();i++){
            double value = Double.parseDouble(list.get(i).toString());
            JSONObject dataJson = new JSONObject();
            JSONArray coo = new JSONArray();
            coo.add(""+basisLabel.get(i));
            coo.add(""+name);
            coo.add((int)value);
            dataJson.put("value", coo);

            if (min == max){
                min = max / 3.0;
            }
            double radius = Math.log10((value-min)/max+1.0)/Math.log10(1.028)+5.0;
            if (Math.abs(value) < 1e-6){
                radius = 0;
            }
            dataJson.put("symbolSize", radius);

            if (Math.abs(radius)>1e-6) {
                Double[] offset = new Double[]{0.0, 0.0};
                if (radius <= 15) {
                    offset[1] = radius + 3;
                }
                JsonUtil.addElement(dataJson, "label.normal.offset", JsonUtil.arrayToJsonArray(offset));
                JsonUtil.addElement(dataJson, "label.normal.show", true);
            } else {
                JsonUtil.addElement(dataJson, "label.normal.show", false);
            }
            dataArray.add(dataJson);
        }
        json.put("data", dataArray);

        JsonUtil.addElement(json, "label.normal.fontSize", 8);
        JsonUtil.addElement(json, "label.normal.color", "#333333");
        JsonUtil.addElement(json, "label.normal.fontWeight", 300);
        JsonUtil.addElement(json, "label.normal.fontFamily", "Arial");
        JsonUtil.addElement(json, "itemStyle.normal.shadowBlur", 10);
        JsonUtil.addElement(json, "itemStyle.normal.shadowColor", "rgba(33, 33, 33, 0.5)");
        JsonUtil.addElement(json, "itemStyle.normal.shadowOffsetY", 5);

        return json;
    }

    private <T> JSONObject getSeriesLine(String name, List<T> list, Symbol symbol){
        JSONObject json = getSeriesLineInBasic(name, list);
        json.put("symbol", symbol.getSymbol());
        json.put("symbolSize", symbol.getSymbolSize());
        json.put("symbolRotate", symbol.getSymbolRotate());
        json.put("symbolOffset", symbol.getSymbolOffset());
        json.put("showAllSymbol", true);
        return json;
    }

    private <T> JSONObject getSeriesLineInBasic(String name, List<T> list){
        JSONObject json = new JSONObject();
        json.put("name", name);
        json.put("type", "line");
        json.put("symbol", "circle");
        json.put("symbolSize", 6);
        json.put("showAllSymbol", true);
        json.put("smooth", true);
        json.put("smooth", "x");
        json.put("data", JsonUtil.listToJsonArray(list));
        return json;
    }

    private <T> JSONObject getSeriesStackline(String name, List<T> list){
        JSONObject json = getSeriesLineInBasic(name, list);
        json.put("stack", "总量");
        json.put("areaStyle", JSONObject.parseObject("{normal: {}}"));
        return json;
    }

    private <T> JSONObject getSeriesBar(String graphType, String name, List<T> list){
        JSONObject json = new JSONObject();
        json.put("name", name);
        json.put("type", "bar");
        json.put("barGap", "10%");
        json.put("barCategoryGap", "45%");
        json.put("barMaxWidth", "50");
        if (graphType.equals(VisualConfig.TYPE_BAR) ||
                graphType.equals(VisualConfig.TYPE_STRIPE) ) {
            JsonUtil.addElement(json, "label.normal.show", true);
        } else {
            JsonUtil.addElement(json, "label.normal.show", false);
        }
        if (graphType.equals(VisualConfig.TYPE_BAR)) {
            JsonUtil.addElement(json, "label.normal.position", "top");
            JsonUtil.addElement(json, "label.normal.color", "#333333");
        } else if (graphType.equals(VisualConfig.TYPE_STRIPE)){
            JsonUtil.addElement(json, "label.normal.position", "right");
            JsonUtil.addElement(json, "label.normal.color", "#333333");
        }

        JSONArray data =  JsonUtil.listToJsonArray(list);
        data = eliminateZeroValueInBar(data);
        json.put("data", data);
        return json;
    }

    private <T> JSONObject getSeriesStackbar(String graphType, String name, List<T> list){
        JSONObject json = getSeriesBar(graphType, name, list);
        json.put("stack", "总量");
        return json;
    }

    private <T> JSONObject getSeriesStackStripeRatio(String name, List<T> list){
        JSONObject json = new JSONObject();
        json.put("name", name);
        json.put("type", "bar");
        json.put("barGap", "10%");
        json.put("label", JSONObject.parseObject("{normal: {show: true}}"));
        json.put("stack", "总量");
        List<Double> ratioList = DataUtil.getPercentageData(list);
        JSONArray data = JsonUtil.listToJsonArray(ratioList);
        data = eliminateZeroValueInBar(data);
        json.put("data", data);
        return json;
    }

    private <T> JSONObject getSeriesPie(String name, List<T> list, List<String> basisList){
        JSONObject json = new JSONObject();
        json.put("name", name);
        json.put("type", "pie");
        json.put("radius", "75%");
        json.put("center", JSONArray.parseArray("['50%', '50%']"));

        JSONObject label = new JSONObject();
        JSONObject normal = new JSONObject();
        normal.put("formatter", "function(a){return wrapText(a.name)}");
        label.put("normal", normal);
        json.put("label", label);

        JSONArray dataArray = new JSONArray();
        for (int i=0;i<list.size();i++){
            if (Integer.parseInt(""+list.get(i))!=0 ) {
                JSONObject data = new JSONObject();
                data.put("value", list.get(i));
                data.put("name", basisList.get(i));
                dataArray.add(data);
            }
        }
        json.put("data", dataArray);
        return json;
    }

    private <T> JSONObject getSeriesPie(String name, List<T> list, JSONArray basisData){
        List<String> basisList = JsonUtil.<String>jsonArrayToList(basisData);
        return getSeriesPie(name, list, basisList);
    }

    protected JSONObject getLegend(String graphType, JSONArray labelArray){
        JSONObject legendJson = new JSONObject();
        legendJson.put("data", labelArray);

        switch (graphType){
            case VisualConfig.TYPE_PIE:
                legendJson.put("formatter", "function (value, index) {return wrapText(value)}");
                legendJson.put("orient", "vertical");
                legendJson.put("type", "scroll");
                legendJson.put("left", "left");
                break;
            case VisualConfig.TYPE_STACKLINE:
            case VisualConfig.TYPE_STACKLINERATIO:
                legendJson.put("icon", "roundRect");
            default:
                legendJson.put("formatter", "function (value, index) {return wrapTextInLegend(value)}");
                legendJson.put("width", "70%");
                legendJson.put("top", 3);
                break;
        }

        return legendJson;
    }

    protected JSONObject getTooltip(String graphType, String basisName, String branchName, String targetName){
        switch (graphType) {
            case VisualConfig.TYPE_PIE:
                return getTooltipPie(targetName);
            case VisualConfig.TYPE_STACKLINERATIO:
            case VisualConfig.TYPE_STACKBARRATIO:
            case VisualConfig.TYPE_STACKSTRIPERATIO:
                return getTooltipClassic(basisName, targetName+"占比");
            case VisualConfig.TYPE_STACKSTRIPE:
            case VisualConfig.TYPE_STACKLINE:
            case VisualConfig.TYPE_STACKBAR:
            case VisualConfig.TYPE_BAR:
            case VisualConfig.TYPE_LINE:
            case VisualConfig.TYPE_STRIPE:
                return getTooltipClassic(basisName, targetName);
            case VisualConfig.TYPE_BUBBLE:
                return getTooltipBubble(basisName, branchName, targetName);
            case VisualConfig.TYPE_2TARGET:
                return getTooltip2Target(basisName, branchName, targetName); //branchName = targetName2
            default:
                return getTooltipBar();
        }
    }

    private JSONObject getTooltip2Target(String basisName, String targetName1, String targetName2){
        JSONObject tooltip = new JSONObject();
        tooltip.put("formatter", "function(param){return hoverDataText2Target(param, \""+basisName+"\",\""+targetName1+"\",\""+targetName2+"\");}");
        return tooltip;
    }

    private JSONObject getTooltipClassic(String basisName, String targetName){
        JSONObject tooltip = new JSONObject();
        tooltip.put("trigger", "axis");
        JsonUtil.addElement(tooltip, "axisPointer.type", "shadow");
        JsonUtil.addElement(tooltip,"formatter", "function(param){return hoverDataText(param, \""+basisName+"\",\""+targetName+"\");}");
        return tooltip;
    }

    private JSONObject getTooltipBubble(String basisName, String branchName, String targetName){
        JSONObject tooltip = new JSONObject();
        tooltip.put("trigger", "axis");
        JsonUtil.addElement(tooltip, "axisPointer.type", "shadow");
        JsonUtil.addElement(tooltip,"formatter", "function(param){" +
                "return hoverDataTextBubble(param, \""+basisName+"\",\""+branchName+"\",\""+targetName+"\");}");
        return tooltip;
    }

    private JSONObject getTooltipLine(String basisName, String targetName){
        JSONObject tooltip = new JSONObject();
        tooltip.put("trigger", "axis");
        JsonUtil.addElement(tooltip, "axisPointer.type", "shadow");
        JsonUtil.addElement(tooltip,"formatter", "function(param){" +
                "return param[0].seriesName+\":\"+wrapText(param[0].name).replace(/[\\r\\n]/g, \"<br/>\")+\"<br/>\"+\""+targetName+":\"+param[0].value;}");
        return tooltip;
    }

    private JSONObject getTooltipPie(String targetName){
        JSONObject tooltip = new JSONObject();
        tooltip.put("trigger", "item");
        tooltip.put("formatter", "function(param){ " +
                "return param.seriesName+\": \"+wrapTextInBox(param.name).replace(/[\\r\\n]/g,\"<br/>\")+\"<br/>\"+\""+targetName+": \"+param.value+\" (\"+param.percent+\"%)\"}");
        return tooltip;
    }

    private JSONObject getTooltipBar() {
        String jsonString = " {" +
                "               trigger: 'axis'," +
                "               axisPointer : {" +
                "                   type : 'shadow'       " +
                "               }" +
                "             }";
        JSONObject tooltipJson = JSONObject.parseObject(jsonString);
        return tooltipJson;
    }

    protected JSONObject getToolbox(){
        JSONObject toolbox = new JSONObject();
        JsonUtil.addElement(toolbox, "feature.dataZoom.yAxisIndex", "none");
        JsonUtil.addElement(toolbox, "feature.restore", new JSONObject());
        JsonUtil.addElement(toolbox, "feature.dataView", new JSONObject());
        JsonUtil.addElement(toolbox, "feature.saveAsImage.pixelRatio", 2);
        return toolbox;
    }

    protected JSONObject getGrid(String graphType, JSONArray labelArray){
        JSONObject grid = new JSONObject();
        grid.put("left", "3%");
        grid.put("right", "120");
        grid.put("bottom", "3%");
        grid.put("containLabel", true);

        if (labelArray == null){
            grid.put("top", "70");

        } else {
            grid.put("top", "100");
        }

        return grid;
    }

    protected JSONObject getTitle(String graphName){
        JSONObject titleJson = new JSONObject();
        titleJson.put("text", graphName);
        titleJson.put("left", "center");
        return titleJson;
    }

//    protected JSONObject getXAxis(String graphType, JSONArray label, String name){
    protected JSONObject getXAxis(String graphType, AxisParam param){
        JSONObject xAxisJson = new JSONObject();
        String axisName = param.getName();
        switch (graphType) {
            case VisualConfig.TYPE_STACKSTRIPERATIO:
                axisName += "占比";
                break;
            default:
                if (param.hasUnit()){
                    axisName += "("+param.getUnit()+")";
                }
                break;
        }

        xAxisJson.put("name", axisName);
        xAxisJson.put("nameTextStyle", JSONObject.parseObject("{color: \"#333333\",fontSize: 16}"));


        if (!graphType.equals(VisualConfig.TYPE_STACKSTRIPERATIO) &&
                !graphType.equals(VisualConfig.TYPE_STACKSTRIPE) &&
                !graphType.equals(VisualConfig.TYPE_STRIPE)) {
            if (!DataUtil.isIntegerList(JsonUtil.jsonArrayToList(param.getLabel()))) {
                JsonUtil.addElement(xAxisJson, "axisLabel.interval", 0);
                if (param.getLabel().size() > 5 && DataUtil.aveLength(JsonUtil.jsonArrayToList(param.getLabel())) >= 4) {
                    JsonUtil.addElement(xAxisJson, "axisLabel.rotate", 45);
                }
            }
        }

        switch (graphType){
            case VisualConfig.TYPE_STACKLINE:
            case VisualConfig.TYPE_STACKLINERATIO:
                xAxisJson.put("boundaryGap", false);
                xAxisJson.put("data", param.getLabel());
                JsonUtil.addElement(xAxisJson, "axisLabel.formatter", "function (value, index) {return wrapText(value)}");
                break;
            case VisualConfig.TYPE_STRIPE:
            case VisualConfig.TYPE_STACKSTRIPE:
            case VisualConfig.TYPE_STACKSTRIPERATIO:
                xAxisJson.put("type", "value");
                JsonUtil.addElement(xAxisJson, "splitLine.lineStyle.type", "dashed");
                break;
            case VisualConfig.TYPE_2TARGET:
                xAxisJson.put("type", "value");
                JsonUtil.addElement(xAxisJson, "splitLine.lineStyle.type", "dashed");
                break;
            case VisualConfig.TYPE_BUBBLE:
                JsonUtil.addElement(xAxisJson, "splitLine.show", true);
                JsonUtil.addElement(xAxisJson, "splitLine.lineStyle.type", "dashed");
                JsonUtil.addElement(xAxisJson, "boundaryGap", false);
                JsonUtil.addElement(xAxisJson, "axisLabel.interval", 0);
                xAxisJson.put("data", JsonUtil.addElementToJsonArray(param.getLabel(), "", 0));
                JsonUtil.addElement(xAxisJson, "axisLabel.formatter", "function (value, index) {return wrapText(value)}");
                break;
            default:
                xAxisJson.put("data", param.getLabel());
                JsonUtil.addElement(xAxisJson, "axisLabel.formatter", "function (value, index) {return wrapText(value)}");
                break;
        }
        return xAxisJson;
    }

    protected JSONObject getYAxis(String graphType, AxisParam param){
        JSONObject yAxisJson = new JSONObject();

        String axisName = param.getName();
        switch (graphType) {
            case VisualConfig.TYPE_STACKBARRATIO:
            case VisualConfig.TYPE_STACKLINERATIO:
                axisName += "占比";
                break;
            default:
                if (param.hasUnit()){
                    axisName += "("+param.getUnit()+")";
                }
                break;
        }

        yAxisJson.put("name", axisName);
        yAxisJson.put("nameTextStyle", JSONObject.parseObject("{color: \"#333333\",fontSize: 16}"));
        JSONObject axisLabel = new JSONObject();
        yAxisJson.put("axisLabel", axisLabel);

        boolean flag = DataUtil.isIntegerList(JsonUtil.jsonArrayToList(param.getLabel()));
        if (!flag){
            axisLabel.put("interval", 0);
        }
        switch (graphType){
            case VisualConfig.TYPE_STRIPE:
            case VisualConfig.TYPE_STACKSTRIPE:
            case VisualConfig.TYPE_STACKSTRIPERATIO:
                axisLabel.put("formatter", "function (value, index) {return wrapText(value)}");
                yAxisJson.put("data", param.getLabel());
                break;
            case VisualConfig.TYPE_BUBBLE:
                JsonUtil.addElement(yAxisJson, "splitLine.show", true);
                JsonUtil.addElement(yAxisJson, "splitLine.lineStyle.type", "dashed");
                JsonUtil.addElement(yAxisJson, "boundaryGap", false);
                JsonUtil.addElement(yAxisJson, "axisLabel.interval", 0);
                axisLabel.put("formatter", "function (value, index) {return wrapText(value)}");
                yAxisJson.put("data", JsonUtil.addElementToJsonArray(param.getLabel(), "", 0));
                break;
            default:
                yAxisJson.put("type", "value");
                JsonUtil.addElement(yAxisJson, "splitLine.lineStyle.type", "dashed");
                break;
        }

        return yAxisJson;
    }

    private JSONArray eliminateZeroValueInBar(JSONArray dataArray){
        for (int i=0;i<dataArray.size();i++){
            Object value = dataArray.get(i);
            if (value instanceof Integer || value instanceof Long || value instanceof Double){
                if (Math.abs(Double.parseDouble(value.toString()))<1e-6){
                    JSONObject obj = new JSONObject();
                    obj.put("value", value);
                    obj.put("itemStyle", JSONObject.parseObject("{normal:{opacity:0}}"));
                    dataArray.set(i, obj);
                }
            }
        }
        return dataArray;
    }

    protected JSONArray getColor(int colorNum){
        System.out.println("color num = "+colorNum);
        JSONArray colorArray = new JSONArray();
        float start = 0.6f;
        float step = 1.0f / colorNum;
        for (int i=0;i<colorNum;i++){
            Color color = Color.decode(""+Color.HSBtoRGB(start,1f,0.7f));
            String red = Integer.toHexString(color.getRed());
            String green = Integer.toHexString(color.getGreen());
            String blue = Integer.toHexString(color.getBlue());
            if (red.length() == 1) red = "0"+red;
            if (green.length() == 1) green = "0"+green;
            if (blue.length() == 1) blue = "0"+blue;
            String colorString = "#"+red+green+blue;

            System.out.println(start+":\t"+color+"\t"+colorString);
            start += step;
            colorArray.add(colorString);
        }

        System.out.println("---------------");
        return colorArray;
    }
}
